package RiverCrossingMinigame;

// Some code was adapted from the Virginia CS homework assignments and 
// tutorials/examples/explanations found here http://www.cs.virginia.edu/cs201j/problem-sets/ps5/

import javax.swing.*;
import java.awt.*;
/**
 * This class represents the grid object used as the backing structure in the river crossing minigame structure
 * The grid holds all of the possible locations for logs and the player
 * 
 * @author Ray DeCuir
 * @version 1.0
 *
 */


@SuppressWarnings("serial")
public class Grid extends JPanel {

	// the x dimension
	protected int xsize;

	// the y dimension
	protected int ysize; 

	
	// the 2D backing array
	Movable[][] simobjects;

	/**
	 * COnstructor that sets the initial size of the grid object
	 * 
	 * @param xsize The x dimension of the grid
	 * @param ysize The y dimension of the grid
	 */
	public Grid(int xsize, int ysize)
	{
		this.xsize = xsize;
		this.ysize = ysize;
		simobjects = new Movable[xsize][ysize];
	}

	
	/**
	 * Getter for the number of horizontal squares are in the grid, the x dimension
	 * 
	 * @return The x dimension of the grid
	 */
	public int numHorizontal(){
		return xsize;
	}

	
	/**
	 * Getter for the number of vertical squares are in the grid, 
	 * 
	 * @return The y dimension of the grid
	 */
	public int numVertical(){
		return ysize;
	}
	
	
	/**
	 * Gets the width for an individual square in the grid
	 * 
	 * @return The width per square
	 */
	private int getSquareWidth(){
		int swidth = getWidth() / numHorizontal();
		if (swidth > 0)
			return swidth;
		else
			return 1;
	}

	/**
	 * Gets the height for an individual square in the grid
	 * 
	 * @return The height per square
	 */
	private int getSquareHeight(){
		int sheight = getHeight() / numVertical();
		if (sheight > 0) 
			return sheight;
		else 
			return 1;
	}

	/**
	 * Gets the horizontal error margin, to be used for redrawing the grid
	 * 
	 * @return The horizontal error margin in the panel
	 */
	private int getHorizontalOffset() {
		return (getWidth() - (getSquareWidth() * numHorizontal())) / 2;
	}

	
	/**
	 * Gets the vertical error margin, to be used for redrawing the grid
	 * 
	 * @return The vertical error margin in the panel
	 */
	private int getVerticalOffset() {
		return (getHeight() - (getSquareHeight() * numVertical())) / 2;
	}

	
	/**
	 * Places the parameterized object at the set coordinates in the grid
	 * 
	 * 
	 * @param x The x coordinate
	 * @param y The y coordinate 
	 * @param movable The object to set at the desired position
	 * @throws Exception Thrown whenever there is already an object in that space, or if that space is invalid
	 */
	synchronized public void setObjectAt(int x, int y, Movable movable) throws Exception
	{
		if (validLocation (x, y)) {
			simobjects[x][y] = movable;
		} 
		else {
			throw new Exception ( x + ", " + y);
		}
	}


	/**
	 * Removes the object at the specified coordinates in the grid
	 * 
	 * @param x The x coordinate
	 * @param y The y coordinate 
	 */
	synchronized public void removeObjectAt(int x, int y)
	{
		simobjects[x][y] = null;
	}


	/**
	 * Checks to see if a particular grid location is valid or not, used for placing and removing objects 
	 * 
	 * @param x The x coordinate
	 * @param y The y coordinate 
	 * @return A boolean denoting if a location is valid or not
	 */
	public boolean validLocation(int x, int y)
	{
		return (x >= 0 && y >= 0 && y < numVertical () && x < numHorizontal ());
	}


	/**
	 * Gets the object at the set coordinates in the grid, unsynchronized version for the GUI
	 * 
	 * 
	 * @param x The x coordinate
	 * @param y The y coordinate 
	 * @return The object at the desired position, null if there is nothing there
	 * @throws RuntimeException Thrown whenever there is already an object in that space, or if that space is invalid
	 */
	/* unsynchronized */
	public Movable grabObjectAt(int x, int y) throws RuntimeException
	{
		if ((x < 0) || (x >= numHorizontal())) {
			throw new RuntimeException("Bad x parameter to getObjectAt: " + x);
		}
		if ((y < 0) || (y >= numVertical())) {
			throw new RuntimeException("Bad y parameter to getObjectAt: " + y);
		}
		return simobjects[x][y];
	}

	/**
	 * Gets the object at the set coordinates in the grid, synchronized version for game engine
	 * 
	 * 
	 * @param x The x coordinate
	 * @param y The y coordinate 
	 * @return The object at the desired position, null if there is nothing there
	 * @throws RuntimeException Thrown whenever there is already an object in that space, or if that space is invalid
	 */
	synchronized public Movable getObjectAt(int x, int y) throws RuntimeException
	{
		return grabObjectAt(x, y);
	}


	/**
	 * Paints a single log square at the desired location on the grid
	 * 
	 * @param g The panel on which to paint
	 * @param x The x coordinate of the grid to paint
	 * @param y The y coordinate of the grid to paint
	 * @param hoffset The x error to account for when painting
	 * @param voffset The y error to account for when painting
	 * @param squarewidth The width of each individual square
	 * @param squareheight The height of each individual square
	 */
	private void paintCell( Graphics g, int x, int y, int hoffset, int voffset, int squarewidth, int squareheight)
	{
		g.setColor(Color.BLUE);
		g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight, squarewidth, squareheight);

		g.setColor(new Color(205, 133, 63));
		g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight +3, squarewidth, squareheight -6);

		g.setColor(Color.black);
		g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight + 3, squarewidth, 1);
		g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight + squareheight - 3, squarewidth, 1);

		g.fillRect( hoffset + x * squarewidth + 4, voffset + y * squareheight + 7, squarewidth - 8, 1);
		g.fillRect( hoffset + x * squarewidth + 9, voffset + y * squareheight + 9, squarewidth - 9, 1);
		g.fillRect( hoffset + x * squarewidth + 2, voffset + y * squareheight + 15, squarewidth - 14, 1);

		g.fillRect( hoffset +  x * squarewidth + 2, voffset + y * squareheight + 18, squarewidth - 16, 1);
		g.drawOval( hoffset +  x * squarewidth + 1, voffset + y * squareheight + 20, 5, 2);
	}

	
	/**
	 * Paints a left side log square at the desired location on the grid
	 * 
	 * @param g The panel on which to paint
	 * @param x The x coordinate of the grid to paint
	 * @param y The y coordinate of the grid to paint
	 * @param hoffset The x error to account for when painting
	 * @param voffset The y error to account for when painting
	 * @param squarewidth The width of each individual square
	 * @param squareheight The height of each individual square
	 */
	private void paintCellLeft( Graphics g, int x, int y, int hoffset, int voffset, int squarewidth, int squareheight)
	{
		g.setColor(Color.BLUE);
		g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight, squarewidth, squareheight);

		g.setColor(new Color(205, 133, 63));
		g.fillRect( hoffset +  x * squarewidth, voffset + y * squareheight +3, squarewidth, squareheight -6);

		g.setColor(Color.black);
		g.fillRect( hoffset +  x * squarewidth, voffset + y * squareheight + 3, squarewidth, 1);
		g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight + squareheight - 3, squarewidth, 1);

		g.fillRect( hoffset +  x * squarewidth + 3, voffset + y * squareheight + 7, squarewidth - 3, 1);
		g.fillRect( hoffset +  x * squarewidth + 12, voffset + y * squareheight + 13, squarewidth - 12, 1);
		g.fillRect( hoffset +  x * squarewidth + 4, voffset + y * squareheight + 15, squarewidth - 18, 1);
		g.fillRect( hoffset +  x * squarewidth + 8, voffset + y * squareheight + 17, squarewidth - 11, 1);
		g.drawOval( hoffset +  x * squarewidth + 1, voffset + y * squareheight + 9, 8, 2);
	}

	
	/**
	 * Paints a right side log square at the desired location on the grid
	 * 
	 * @param g The panel on which to paint
	 * @param x The x coordinate of the grid to paint
	 * @param y The y coordinate of the grid to paint
	 * @param hoffset The x error to account for when painting
	 * @param voffset The y error to account for when painting
	 * @param squarewidth The width of each individual square
	 * @param squareheight The height of each individual square
	 */
	private void paintCellRight( Graphics g, int x, int y, int hoffset, int voffset, int squarewidth, int squareheight)
	{
		g.setColor(Color.BLUE);
		g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight, squarewidth, squareheight);

		g.setColor(new Color(205, 133, 63));
		g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight +3, squarewidth, squareheight -6);

		g.setColor(Color.black);
		g.fillRect( hoffset +  x * squarewidth, voffset + y * squareheight + 3, squarewidth, 1);
		g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight + squareheight - 3, squarewidth, 1);

		g.fillRect( hoffset +  x * squarewidth, voffset + y * squareheight + 7, squarewidth - 3, 1);
		g.fillRect( hoffset +  x * squarewidth, voffset + y * squareheight + 13, squarewidth - 6, 1);
		g.fillRect( hoffset +  x * squarewidth + 7, voffset + y * squareheight + 21, squarewidth - 17, 1);
		g.fillRect( hoffset +  x * squarewidth + 2, voffset + y * squareheight + 18, squarewidth - 16, 1);
		g.drawOval( hoffset +  x * squarewidth + 1, voffset + y * squareheight + 20, 5, 2);
	}
	
	
	/**
	 * Paints the player's vehicle at the desired grid location 
	 * 
	 * @param g The panel on which to paint
	 * @param x The x coordinate of the grid to paint
	 * @param y The y coordinate of the grid to paint
	 * @param hoffset The x error to account for when painting
	 * @param voffset The y error to account for when painting
	 * @param squarewidth The width of each individual square
	 * @param squareheight The height of each individual square
	 */
	private void paintVehicle( Graphics g, int x, int y, int hoffset, int voffset, int squarewidth, int squareheight)
	{
		if ( y==0 || y ==8)
		{
			g.setColor(new Color(245, 222, 179));
			g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight, squarewidth, squareheight);
		}
		else
		{
			g.setColor(Color.BLUE);
			g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight, squarewidth, squareheight);
			for (int i = 0 ; i < (squareheight - 2)/4 ; i++)
			{
				g.setColor(new Color(205, 133, 63));
				g.fillRect( hoffset + x * squarewidth + 1 + 2 * ( i % 2 ), voffset + y * squareheight + 1 + 4 * i, squarewidth - 4 + 2 * ( i % 2 ), 4);
				g.setColor(Color.black);
				g.drawRect( hoffset + x * squarewidth + 2 * ( i % 2 ), voffset + y * squareheight + 1 + 4 * i, squarewidth - 3 + 2 * ( i % 2 ), 4);
			}
		}
		g.setColor(Color.black);
		g.fillRect( hoffset + x * squarewidth + 4, voffset + y * squareheight +6, squarewidth-8, 3);
		g.fillRect( hoffset + x * squarewidth + 4, voffset + (y+1) * squareheight -6, squarewidth-8, 3);

		g.setColor(Color.darkGray);
		g.fillRect( hoffset +  x * squarewidth+6, voffset + y * squareheight + 3, squarewidth-12, squareheight - 6);
		
		g.setColor(Color.lightGray);
		g.fillRect( hoffset +  x * squarewidth+8, voffset + y * squareheight + squareheight / 2, squarewidth-16, squareheight / 2 - 3);
		
		g.setColor(Color.yellow);
		g.fillRect( hoffset +  x * squarewidth+6, voffset + y * squareheight + 3, 4, 2);
		g.fillRect( hoffset +  ( x + 1 ) * squarewidth - 10, voffset + y * squareheight + 3, 4, 2);
	}

	
	/**
	 * 
	 * This method repaints the grid each time the GUI needs to be updated
	 * This method is responsible for drawing the player and the logs for the river crossing minigame
	 * 
	 * @RuntimeException If an object can not be set
	 */
	public void paintComponent(Graphics g) throws RuntimeException 
	{
		int squarewidth = getSquareWidth();
		int squareheight = getSquareHeight();

		int hoffset = getHorizontalOffset();
		int voffset = getVerticalOffset();

		for (int y = 0; y < numVertical(); y++) {
			for (int x = 0; x < numHorizontal(); x++) 
			{
				Movable tmp = grabObjectAt(x, y);
				if (tmp == null) 
				{
					if ( y == 0 || y == 8)
					{
						//drawing the beach with the sand
						g.setColor(new Color(245, 222, 179));
						g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight, squarewidth, squareheight);
						g.setColor(Color.black);
						for ( int i = 0 ; i < 30 ; i++)
						{
							int xPos = hoffset + x * squarewidth + ((int)( 3137 * ( i * y + 1 ) * ( i + 1 ) / (1 + i + 1) )) % (squarewidth);
							int yPos = voffset + y * squareheight + ((int)( 3711 * ( i * y + 1 ) * ( i + 4 ) / (1 + 2 * x + i) )) % (squareheight);
							g.fillRect( xPos , yPos , 1, 1);
						}
					}
					else 
					{
						// drawing the blue square with the waves
						g.setColor(Color.BLUE);
						g.fillRect( hoffset + x * squarewidth, voffset + y * squareheight, squarewidth, squareheight);

						g.setColor(Color.BLACK);
						g.drawOval( hoffset +  x * squarewidth + 6, voffset + y * squareheight + 18, 12, 5);
						g.drawOval( hoffset +  x * squarewidth + 18, voffset + y * squareheight + 18, 12, 5);
						g.drawOval( hoffset +  x * squarewidth + 0, voffset + y * squareheight + 5, 12, 5);
						g.drawOval( hoffset +  x * squarewidth + 12, voffset + y * squareheight + 5, 12, 5);

						g.setColor(Color.BLUE);
						g.fillRect( hoffset + x * squarewidth + 6, voffset + y * squareheight + 18, 24, 3);
						g.fillRect( hoffset + x * squarewidth + 0, voffset + y * squareheight + 5, 24, 3);
					}
				} 
				// draw the log
				else if ( tmp.getColor() == Color.RED)
				{
					// double log
					if ( x > 1 && grabObjectAt(x - 2, y) != null && grabObjectAt(x - 2, y).getColor() == Color.red)
					{
						paintCellLeft(g, x - 1, y, hoffset, voffset, squarewidth, squareheight);
						paintCellRight(g, x, y, hoffset, voffset, squarewidth, squareheight);
					}
					// single log square
					else
					{
						paintCell(g, x, y, hoffset, voffset, squarewidth, squareheight);
					}
				}
				// draw the vehicle
				else
				{
					paintVehicle(g, x, y, hoffset, voffset, squarewidth, squareheight);
				}
			}
		}
	}
}